import { SchemaForUI, Schema, Template, BLANK_PDF, BasePdf, pluginRegistry } from '@pdfme/common';
import { uuid, getUniqueSchemaName, schemasList2template, changeSchemas, setFontNameRecursively } from '../src/helper';
import { text, image } from '@pdfme/schemas';

const getSchema = (): Schema => ({
  name: 'a',
  type: 'text',
  content: '',
  position: { x: 0, y: 0 },
  width: 100,
  height: 100,
});

describe('getUniqSchemaName test', () => {
  test('getUniqSchemaName case1', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'b', content: 'b' }];
    const stackUniqSchemaNames: string[] = [];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy');
  });

  test('getUniqSchemaName case2', () => {
    const copiedSchemaName = 'a copy';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'a copy', content: 'a' }];
    const stackUniqSchemaNames: string[] = [];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 2');
  });

  test('getUniqSchemaName case3', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [
      { id: uuid(), ...getSchema(), name: 'a', content: 'a' },
      { id: uuid(), ...getSchema(), name: 'a copy 2', content: 'a' },
    ];
    const stackUniqSchemaNames: string[] = [];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 3');
  });

  test('getUniqSchemaName case4', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [
      { id: uuid(), ...getSchema(), name: 'a', content: 'a' },
      { id: uuid(), ...getSchema(), name: 'a copy 2', content: 'a' },
    ];
    const stackUniqSchemaNames: string[] = ['a copy 3'];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 4');
  });

  test('getUniqSchemaName case5', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [
      { id: uuid(), ...getSchema(), name: 'a', content: 'a' },
      { id: uuid(), ...getSchema(), name: 'a copy 3', content: 'a' },
    ];
    const stackUniqSchemaNames: string[] = [];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 4');
  });

  test('getUniqSchemaName case6', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [
      { id: uuid(), ...getSchema(), name: 'a', content: 'a' },
      { id: uuid(), ...getSchema(), name: 'a copy 3', content: 'a' },
    ];
    const stackUniqSchemaNames: string[] = ['a copy 4'];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 5');
  });

  test('getUniqSchemaName case7', () => {
    const copiedSchemaName = 'a';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'a', content: 'a' }];
    const stackUniqSchemaNames: string[] = ['a copy 2', 'a copy 3', 'a copy 4'];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 5');
  });

  test('getUniqSchemaName case8', () => {
    const copiedSchemaName = 'a copy 2';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'a copy 2', content: 'a' }];
    const stackUniqSchemaNames: string[] = ['a copy 3'];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 4');
  });

  test('getUniqSchemaName case9', () => {
    const copiedSchemaName = 'a copy 9';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'a copy 9', content: 'a' }];
    const stackUniqSchemaNames: string[] = ['a copy 10'];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 11');
  });

  test('getUniqSchemaName case10', () => {
    const copiedSchemaName = 'a copy 10';
    const schema: SchemaForUI[] = [{ id: uuid(), ...getSchema(), name: 'a copy 10', content: 'a' }];
    const stackUniqSchemaNames: string[] = [];
    const uniqSchemaName = getUniqueSchemaName({ copiedSchemaName, schema, stackUniqueSchemaNames: stackUniqSchemaNames });
    expect(uniqSchemaName).toBe('a copy 11');
  });
});

describe('schemasList2template test', () => {
  test('schemasList2template normal', () => {
    const template: Template = {
      basePdf: BLANK_PDF,
      schemas: [[getSchema()]],
    };
    const schemasList: SchemaForUI[][] = [[{ id: uuid(), ...getSchema(), name: 'b', content: 'b' }]];
    expect(schemasList2template(schemasList, template.basePdf)).toStrictEqual({
      basePdf: BLANK_PDF,
      schemas: [
        [ { name: 'b', type: 'text', position: { x: 0, y: 0 }, width: 100, height: 100, content: 'b' } ],
      ],
    });
  });

  test('schemasList2template readOnly', () => {
    const template: Template = {
      basePdf: BLANK_PDF,
      schemas: [[getSchema()]],
    };
    const schemasList: SchemaForUI[][] = [
      [{ id: uuid(), readOnly: true, ...getSchema(), name: 'b', content: 'b' }],
    ];
    expect(schemasList2template(schemasList, template.basePdf)).toStrictEqual({
      basePdf: BLANK_PDF,
      schemas: [
        [
          {
            name: 'b',
            type: 'text',
            position: { x: 0, y: 0 },
            width: 100,
            height: 100,
            readOnly: true,
            content: 'b',
          },
        ],
      ],
    });
  });
});

describe('changeSchemas test', () => {
  const schemaA = { id: uuid(), ...getSchema(), name: 'a', content: 'a' };
  const schemaB = { id: uuid(), ...getSchema(), name: 'b', content: 'b' };
  const schemas: SchemaForUI[] = [schemaA, schemaB];
  const basePdf1: BasePdf = BLANK_PDF;
  const basePdf2: BasePdf = { width: 210, height: 297, padding: [10, 10, 10, 10] };
  const pluginsRegistry = pluginRegistry({ text, image });
  const pageSize = { width: 210, height: 297 };

  test('changeSchemas - change content with objs length = 1', () => {
    const objs = [{ key: 'content', value: 'a!', schemaId: schemaA.id }];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a!',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change content with objs length = 2', () => {
    const objs = [
      { key: 'content', value: 'a!', schemaId: schemaA.id },
      { key: 'content', value: 'b!', schemaId: schemaB.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a!',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b!',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change width with objs length = 1', () => {
    const objs = [{ key: 'width', value: 150, schemaId: schemaA.id }];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 0, y: 0 },
        width: 150,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change width and height with objs length = 2', () => {
    const objs = [
      { key: 'width', value: 150, schemaId: schemaA.id },
      { key: 'height', value: 75, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 0, y: 0 },
        width: 150,
        height: 75,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change type with objs length = 1', () => {
    const objs = [{ key: 'type', value: 'image', schemaId: schemaA.id }];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'image',
        content:
        '',
        position: { x: 0, y: 0 },
        width: 40,
        height: 40,
        opacity: 1,
        rotate: 0,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change position.x and position.y with objs length = 2', () => {
    const objs = [
      { key: 'position.x', value: 5, schemaId: schemaA.id },
      { key: 'position.y', value: 5, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf1,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 5, y: 5 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change position.x and position.y(padding in) with objs length = 2 and BlankPDF', () => {
    const objs = [
      { key: 'position.x', value: 5, schemaId: schemaA.id },
      { key: 'position.y', value: 5, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf2,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 10, y: 10 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change position.x and position.y(padding out) with objs length = 2 and BlankPDF', () => {
    const objs = [
      { key: 'position.x', value: 10, schemaId: schemaA.id },
      { key: 'position.y', value: 10, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf2,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 10, y: 10 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });

  test('changeSchemas - change width and height(padding out) with objs length = 2 and BlankPDF', () => {
    const objs = [
      { key: 'width', value: 110, schemaId: schemaA.id },
      { key: 'height', value: 110, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf2,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 0, y: 0 },
        width: 110,
        height: 110,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });
  test('changeSchemas - change width and height(padding in) with objs length = 2 and BlankPDF', () => {
    const objs = [
      { key: 'position.x', value: 100, schemaId: schemaA.id },
      { key: 'position.y', value: 197, schemaId: schemaA.id },
      { key: 'width', value: 110, schemaId: schemaA.id },
      { key: 'height', value: 110, schemaId: schemaA.id },
    ];
    const mockCallback = jest.fn();

    changeSchemas({
      schemas,
      objs,
      commitSchemas: mockCallback,
      basePdf: basePdf2,
      pluginsRegistry,
      pageSize,
    });
    expect(mockCallback.mock.calls[0][0]).toStrictEqual([
      {
        id: schemaA.id,
        name: 'a',
        type: 'text',
        content: 'a',
        position: { x: 100, y: 187 },
        width: 100,
        height: 100,
      },
      {
        id: schemaB.id,
        name: 'b',
        type: 'text',
        content: 'b',
        position: { x: 0, y: 0 },
        width: 100,
        height: 100,
      },
    ]);
  });
});

describe('setFontNameRecursively', () => {
  it('sets fontName in object with undefined fontName property', () => {
    const obj = { fontName: undefined, content: 'test' };
    setFontNameRecursively(obj, 'Arial');
    expect(obj.fontName).toEqual('Arial');
  });

  it('does not modify existing fontName values', () => {
    const obj = { fontName: 'Helvetica', content: 'test' };
    setFontNameRecursively(obj, 'Arial');
    expect(obj.fontName).toEqual('Helvetica');
  });

  it('recursively sets fontName in nested objects and preserves other properties', () => {
    const obj = {
      outer: {
        fontName: undefined,
        otherProp: 'unchanged',
        inner: {
          fontName: undefined,
          content: 'test',
          style: { color: 'red' }
        }
      }
    };
    setFontNameRecursively(obj, 'Arial');
    expect(obj.outer.fontName).toEqual('Arial');
    expect(obj.outer.otherProp).toEqual('unchanged');
    expect(obj.outer.inner.fontName).toEqual('Arial');
    expect(obj.outer.inner.content).toEqual('test');
    expect(obj.outer.inner.style.color).toEqual('red');
  });

  it('handles arrays of objects', () => {
    const obj = {
      items: [
        { fontName: undefined, content: 'item1' },
        { fontName: undefined, content: 'item2' }
      ]
    };
    setFontNameRecursively(obj, 'Arial');
    expect(obj.items[0].fontName).toEqual('Arial');
    expect(obj.items[1].fontName).toEqual('Arial');
  });

  it('ignores null values', () => {
    const obj = { fontName: undefined, nullProp: null };
    setFontNameRecursively(obj, 'Arial');
    expect(obj.fontName).toEqual('Arial');
    expect(obj.nullProp).toBeNull();
  });

  it('handles empty objects', () => {
    const obj = {};
    setFontNameRecursively(obj, 'Arial');
    expect(obj).toEqual({});
  });

  it('returns early for null input or undefined input', () => {
    expect(() => setFontNameRecursively(null as any, 'Arial')).not.toThrow();
    expect(() => setFontNameRecursively(undefined as any, 'Arial')).not.toThrow();
  });
});